﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;

namespace Scripting
{
    public class Script
    {
        public delegate void CompilerMessage(string message);

        public event CompilerMessage Error;
        public event CompilerMessage Warning;
        public event CompilerMessage Output;

        protected void OnMessage(CompilerMessage cmpMessage, string message)
        {
            if (cmpMessage != null)
            {
                cmpMessage(message);
            }
        }

        protected string _code;

        public string Code
        {
            get { return _code; }
        }

        protected Assembly _assembly;

        public Assembly Assembly
        {
            get { return _assembly; }
        }

        protected ScriptContext _context = new ScriptContext();

        public ScriptContext Context
        {
            get { return _context; }
            set { _context = value; }
        }

        public void LoadCode(string filename)
        {
            try
            {
                // Create an instance of StreamReader to read from a file.
                // The using statement also closes the StreamReader.
                using (StreamReader sr = new StreamReader(filename))
                {
                    String line;
                    // Read and display lines from the file until the end of
                    // the file is reached.
                    while ((line = sr.ReadLine()) != null)
                    {
                        _code += line;
                    }
                }
            }
            catch (Exception e)
            {
            }
        }

        public void CompileCode(Assembly parent)
        {
            // Create a code provider
            // This class implements the 'CodeDomProvider' class as its base. All of the current .Net languages (at least Microsoft ones)
            // come with thier own implemtation, thus you can allow the user to use the language of thier choice (though i recommend that
            // you don't allow the use of c++, which is too volatile for scripting use - memory leaks anyone?)
            CSharpCodeProvider csProvider = new CSharpCodeProvider();

            // Setup our options
            CompilerParameters options = new CompilerParameters();
            options.GenerateExecutable = false; // we want a Dll (or "Class Library" as its called in .Net)
            options.GenerateInMemory = true; // Saves us from deleting the Dll when we are done with it, though you could set this to false and save start-up time by next time by not having to re-compile
            // And set any others you want, there a quite a few, take some time to look through them all and decide which fit your application best!

            // Add any references you want the users to be able to access, be warned that giving them access to some classes can allow
            // harmful code to be written and executed. I recommend that you write your own Class library that is the only reference it allows
            // thus they can only do the things you want them to.
            // (though things like "System.Xml.dll" can be useful, just need to provide a way users can read a file to pass in to it)
            // Just to avoid bloatin this example to much, we will just add THIS program to its references, that way we don't need another
            // project to store the interfaces that both this class and the other uses. Just remember, this will expose ALL public classes to
            // the "script"
            foreach (AssemblyName an in parent.GetReferencedAssemblies())
            {
                Assembly assembly = Assembly.Load(an);
                options.ReferencedAssemblies.Add(assembly.Location);
            }


            // Compile our code
            CompilerResults result;
            result = csProvider.CompileAssemblyFromSource(options, _code);

            if (result.Errors.HasErrors)
            {
                // TODO: report back to the user that the script has errors
                foreach (CompilerError ce in result.Errors)
                {
                    if(!ce.IsWarning)
                        OnMessage(Error, ce.ToString());
                }
                
                
                _assembly = null;
            }
            else
            {
                _assembly = result.CompiledAssembly;
            }

            if (result.Errors.HasWarnings)
            {
                // TODO: tell the user about the warnings, might want to prompt them if they want to continue
                // runnning the "script"
                // TODO: report back to the user that the script has errors
                foreach (CompilerError ce in result.Errors)
                {
                    if (ce.IsWarning)
                        OnMessage(Warning, ce.ToString());
                }
            }

            foreach (string strOutput in result.Output)
            {
                OnMessage(Output, strOutput);
            }
        }

        public void RunAssembly()
        {
            // Now that we have a compiled script, lets run them
            if (_assembly != null)
            {
                foreach (Type type in _assembly.GetExportedTypes())
                {
                    foreach (Type iface in type.GetInterfaces())
                    {
                        if (iface == typeof(Scripting.IScript))
                        {
                            // yay, we found a script interface, lets create it and run it!

                            // Get the constructor for the current type
                            // you can also specify what creation parameter types you want to pass to it,
                            // so you could possibly pass in data it might need, or a class that it can use to query the host application
                            ConstructorInfo constructor = type.GetConstructor(System.Type.EmptyTypes);
                            if (constructor != null && constructor.IsPublic)
                            {
                                // lets be friendly and only do things legitimitely by only using valid constructors

                                // we specified that we wanted a constructor that doesn't take parameters, so don't pass parameters
                                Scripting.IScript scriptObject = constructor.Invoke(null) as Scripting.IScript;
                                if (scriptObject != null)
                                {
                                    //Lets run our script and display its results
                                    //MessageBox.Show(scriptObject.RunScript(50));
                                    scriptObject.RunScript(_context);
                                }
                                else
                                {
                                    // hmmm, for some reason it didn't create the object
                                    // this shouldn't happen, as we have been doing checks all along, but we should
                                    // inform the user something bad has happened, and possibly request them to send
                                    // you the script so you can debug this problem
                                }
                            }
                            else
                            {
                                // and even more friendly and explain that there was no valid constructor
                                // found and thats why this script object wasn't run
                            }
                        }
                    }
                }
            }
        }
    }
}
